home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 4
/
Aminet 4 - November 1994.iso
/
aminet
/
dev
/
obero
/
oberon_lib.lha
/
oberon-a
/
source1.lha
/
source
/
ProjectOberon
/
Files.mod
next >
Wrap
Text File
|
1994-08-08
|
19KB
|
690 lines
(***************************************************************************
$RCSfile: Files.mod $
Description: Port of the Project Oberon Files module
Created by: J. Gutknecht
Ported by: fjc (Frank Copeland)
$Revision: 1.6 $
$Author: fjc $
$Date: 1994/08/08 16:41:14 $
Copyright © 1990-1993, ETH Zuerich
Copyright © 1994, Frank Copeland.
This file is part of the Oberon-A Library.
See Oberon-A.doc for conditions of use and distribution.
Log entries are at the end of the file.
***************************************************************************)
MODULE Files;
(*
Interface notes
===============
This module attempts to reproduce the behaviour of the Project Oberon
module as closely as possible, with two major exceptions. AmigaDOS does
not allow multiple simultaneous access to a file. Until I can work out
some way of recycling AmigaDOS FileHandles, only one user will be allowed
per file. This implementation of Oberon does not include a resource
tracker. This means that files must be explicitly closed, using either
Register (), Close () or Purge (). File variables remain allocated after
calls to these procedures, but cannot be used again; they should be
explicitly de-allocated with SYSTEM.DISPOSE ().
Implementation Notes
====================
This module is built as a layer on top of AmigaDOS. Old() attempts to
open the named file with a read/write (but not exclusive) lock. New()
creates a temporary file. Both will fail if they attempt to open an
interactive file. Register() deletes any existing file and renames the
temporary file. Purge() deletes the file. Register(), Close() and
Purge() explicitly close the AmigaDOS file but do not de-allocate the File
variable; this allows the programmer to check for any errors that occur
during the close operation.
[TBD]
*)
(*
** $C= CaseChk $I= IndexChk $L= LongAdr $N= NilChk
** $P= PortableCode $R= RangeChk $S= StackChk $T= TypeChk
** $V= OvflChk $Z= ZeroVars
*)
IMPORT SYS := SYSTEM, Dos, DosUtil, Str := Strings, Oberon;
(* --- Public declarations ---------------------------------------------- *)
TYPE
File * = POINTER TO Handle; (* Note that file.dosError is public. *)
Buffer = POINTER TO BufferRecord;
Rider * = RECORD
eof * : BOOLEAN;
res * : LONGINT; (* Set to the error code returned by AmigaDOS *)
file : File;
apos : LONGINT;
buf : Buffer;
bpos : INTEGER;
END; (* Rider *)
(* --- Private declarations and procedures ------------------------------ *)
(*
These definitions are taken from the Project Oberon module FileDir, which
does not exist in this implementation.
*)
CONST
FnLength = 256; (* for AmigaDOS, = 32 for Project Oberon *)
SectorSize = 1024;
TYPE
FileName = ARRAY FnLength OF CHAR;
DataSector = ARRAY SectorSize OF SYS.BYTE;
(*------------------------------------*)
CONST
MaxBufs = 4;
TYPE
DiskAdr = LONGINT;
Handle = RECORD
len : LONGINT;
nofbufs : INTEGER;
firstbuf : Buffer;
name : FileName;
time, date : LONGINT;
fileHandle : Dos.FileHandlePtr;
dosError * : LONGINT; (* The AmigaDOS error code for the most
* recent operation
*)
tempKey : LONGINT;
next : File;
END; (* Handle *)
BufferRecord = RECORD
apos : LONGINT;
lim : INTEGER;
mod : BOOLEAN;
next : Buffer;
data : DataSector;
END; (* BufferRecord *)
VAR
tempKey : LONGINT; (* Used to generate temporary file names. *)
files : File;
(*------------------------------------*)
PROCEDURE Unlink (f : File);
VAR f0 : File;
BEGIN (* Unlink *)
IF f # NIL THEN
IF files # NIL THEN
IF f = files THEN
files := files.next
ELSE
f0 := files;
WHILE (f0.next # NIL) & (f0.next # f) DO
f0 := f0.next
END;
IF f0.next = f THEN f0.next := f.next END;
END;
END;
f.next := NIL
END;
END Unlink;
(*------------------------------------*)
PROCEDURE ReadBuf (f : File; buf : Buffer; pos : LONGINT);
VAR res : LONGINT;
BEGIN (* ReadBuf *)
res := Dos.base.Seek (f.fileHandle, pos, Dos.offsetBeginning);
IF res # -1 THEN
(* ASSERT (buf # NIL, 137); *)
buf.lim := SHORT (Dos.base.Read (f^.fileHandle, buf.data, SectorSize));
buf.apos := pos;
buf.mod := FALSE;
ELSE
f.dosError := Dos.base.IoErr ()
END
END ReadBuf;
(*------------------------------------*)
PROCEDURE WriteBuf (f : File; buf : Buffer);
VAR res : LONGINT;
BEGIN (* WriteBuf *)
(* ASSERT (buf # NIL, 137); *)
res := Dos.base.Seek (f.fileHandle, buf.apos, Dos.offsetBeginning);
IF res # -1 THEN
res := Dos.base.Write (f.fileHandle, buf.data, buf.lim);
IF res = buf.lim THEN
buf.mod := FALSE;
ELSE
f.dosError := Dos.base.IoErr ();
END
ELSE
f.dosError := Dos.base.IoErr ();
END
END WriteBuf;
(*------------------------------------*)
PROCEDURE GetBuf (f : File; pos : LONGINT) : Buffer;
VAR buf, last, next : Buffer;
BEGIN (* GetBuf *)
buf := f.firstbuf;
LOOP
(* ASSERT (buf # NIL, 137); *)
IF buf.apos = pos THEN EXIT END;
IF buf.next = f.firstbuf THEN
last := buf;
IF f.nofbufs < MaxBufs THEN (* allocate new buffer *)
NEW (buf);
(* ASSERT (buf # NIL, 137); *)
INC (f.nofbufs);
ELSE (* take one of the buffers (assuming more than one) *)
buf := f.firstbuf; f.firstbuf := buf.next; last.next := buf.next;
IF buf.mod THEN WriteBuf (f, buf) END
END;
IF pos < f.firstbuf.apos THEN
f.firstbuf := buf
ELSIF pos < last.apos THEN
WHILE last.next.apos < pos DO last := last.next END;
END;
buf.next := last.next; last.next := buf;
buf.apos := pos; buf.lim := 0; buf.mod := FALSE;
IF pos < f.len THEN ReadBuf (f, buf, pos) END;
EXIT
END;
buf := buf.next
END; (* LOOP *)
RETURN buf;
END GetBuf;
(*------------------------------------*)
PROCEDURE Unbuffer (f : File);
VAR buf : Buffer;
BEGIN (* Unbuffer *)
buf := f.firstbuf;
REPEAT
(* ASSERT (buf # NIL, 137); *)
IF buf.mod THEN WriteBuf (f, buf) END;
buf := buf.next
UNTIL buf = f.firstbuf
END Unbuffer;
(*------------------------------------*)
PROCEDURE MakeTempName (VAR name : ARRAY OF CHAR; key : LONGINT);
VAR i : INTEGER; digit : LONGINT;
BEGIN (* MakeTempName *)
COPY ("T:", name);
i := 10;
WHILE i > 0 DO
digit := key MOD 10H; IF digit >= 10 THEN INC (digit, 7) END;
DEC (i); name [i] := CHR (digit + ORD ("0")); key := key DIV 10H
END; (* WHILE *)
name [10] := 0X; Str.Append (name, ".tmp")
END MakeTempName;
(* --- Public procedures ------------------------------------------------ *)
(*------------------------------------*)
(* $D- disable copying of open arrays *)
PROCEDURE Delete * (name : ARRAY OF CHAR; VAR res : LONGINT);
BEGIN (* Delete *)
IF Dos.base.DeleteFile (name) THEN
res := 0
ELSE
res := Dos.base.IoErr ();
IF res = Dos.errorObjectNotFound THEN res := 0 END
END; (* ELSE *)
END Delete;
(*------------------------------------*)
(* $D- disable copying of open arrays *)
PROCEDURE Rename * (old, new : ARRAY OF CHAR; VAR res : LONGINT);
BEGIN (* Rename *)
IF Dos.base.Rename (old, new) THEN
res := 0
ELSE
res := Dos.base.IoErr ()
END
END Rename;
(*------------------------------------*)
PROCEDURE Old * (name : ARRAY OF CHAR) : File;
(*
[TBD]
* This really needs better error handling. I expect testing will
force me to provide it :-)
* Implement check for interactive files.
*)
VAR
f : File; fl : Dos.FileLockPtr; fh : Dos.FileHandlePtr;
fib : Dos.FileInfoBlockPtr; buf : Buffer;
(* $D- disable copying of open arrays *)
BEGIN (* Old *)
f := NIL;
fl := Dos.base.Lock (name, Dos.sharedLock);
IF fl # NIL THEN
fh := Dos.base.Open (name, Dos.modeOldFile);
IF fh # NIL THEN
NEW (fib);
IF fib # NIL THEN
IF Dos.base.Examine (fl, fib^) THEN
Dos.base.UnLock (fl);
NEW (buf);
(* ASSERT (buf # NIL, 137); *)
buf.apos := 0; buf.next := buf; buf.mod := FALSE;
NEW (f);
(* ASSERT (f # NIL, 137); *)
f.len := fib.size;
Oberon.ADOS2OberonTime (fib.date, f.time, f.date);
IF f.len > SectorSize THEN buf.lim := SectorSize
ELSE buf.lim := SHORT (f.len)
END;
f.firstbuf := buf; f.nofbufs := 1; f.name [0] := 0X;
f.fileHandle := fh; f.dosError := 0; f.tempKey := 0;
f.next := files; files := f;
ReadBuf (f, buf, 0);
ELSE
SYS.PUTREG (0, Dos.base.Close (fh));
END; (* IF *)
SYS.DISPOSE (fib);
ELSE
SYS.PUTREG (0, Dos.base.Close (fh));
END; (* IF *)
END; (* IF *)
END; (* IF *)
RETURN f;
END Old;
(*------------------------------------*)
PROCEDURE New * (name : ARRAY OF CHAR) : File;
(*
[TBD]
* This really needs better error handling. I expect testing will
force me to provide it :-)
* Implement check for interactive files.
*)
VAR
tempName : FileName; f : File; fh : Dos.FileHandlePtr; buf : Buffer;
ch : CHAR; i : INTEGER;
(* $D- disable copying of open arrays *)
BEGIN (* New *)
f := NIL;
IF name [0] = 0X THEN
REPEAT MakeTempName (tempName, tempKey); INC (tempKey)
UNTIL ~DosUtil.FileExists (tempName);
fh := Dos.base.Open (tempName, Dos.modeNewFile);
ELSE
COPY (name, tempName); Str.Append (tempName, "$tmp*");
i := SHORT (Str.Length (tempName)) - 1; ch := "A";
REPEAT tempName [i] := ch; ch := CHR (ORD (ch) + 1)
UNTIL ~DosUtil.FileExists (tempName);
fh := Dos.base.Open (tempName, Dos.modeNewFile);
END; (* ELSE *)
IF fh # NIL THEN
NEW (buf);
(* ASSERT (buf # NIL, 137); *)
buf.apos := 0; buf.next := buf; buf.mod := TRUE; buf.lim := 0;
NEW (f);
(* ASSERT (f # NIL, 137); *)
Oberon.GetClock (f.time, f.date);
f.len := 0; f.firstbuf := buf; f.nofbufs := 1; COPY (name, f.name);
f.fileHandle := fh; f.dosError := 0;
IF name [0] = 0X THEN f.tempKey := tempKey-1
ELSE f.tempKey := ORD (ch) - 1
END;
f.next := files; files := f;
ReadBuf (f, buf, 0);
END; (* IF *)
RETURN f;
END New;
(*------------------------------------*)
PROCEDURE Register * (f : File);
VAR tempName, bkpName : FileName; i : INTEGER;
BEGIN (* Register *)
IF (f # NIL) & (f.fileHandle # NIL) THEN
Unlink (f); Unbuffer (f);
IF Dos.base.Close (f.fileHandle) THEN
f.dosError := 0;
IF f.name [0] = 0X THEN
MakeTempName (tempName, f.tempKey);
Delete (tempName, f.dosError)
ELSE
COPY (f.name, tempName); Str.Append (tempName, "$tmp*");
i := SHORT (Str.Length (tempName)) - 1;
tempName [i] := CHR (f.tempKey);
COPY (f.name, bkpName); Str.Append (bkpName, "$bak");
Rename (f.name, bkpName, f.dosError);
IF (f.dosError = 0) THEN
Rename (tempName, f.name, f.dosError);
IF f.dosError = 0 THEN
Delete (bkpName, f.dosError)
END; (* IF *)
ELSIF (f.dosError = Dos.errorObjectNotFound) THEN
Rename (tempName, f.name, f.dosError);
END; (* IF *)
END; (* IF *)
ELSE f.dosError := Dos.base.IoErr ()
END;
END; (* IF *)
END Register;
(*------------------------------------*)
PROCEDURE Close * (f : File);
BEGIN (* Close *)
IF f # NIL THEN
Unlink (f); Unbuffer (f);
IF Dos.base.Close (f.fileHandle) THEN f.dosError := 0
ELSE f.dosError := Dos.base.IoErr ()
END; (* ELSE *)
END; (* IF *)
END Close;
(*------------------------------------*)
PROCEDURE Purge * (f : File);
VAR tempName : FileName; i : INTEGER;
BEGIN (* Purge *)
IF f # NIL THEN
Unlink (f); Unbuffer (f);
IF Dos.base.Close (f.fileHandle) THEN
f.dosError := 0;
IF f.name [0] = 0X THEN
MakeTempName (tempName, f.tempKey);
Delete (tempName, f.dosError)
ELSE
COPY (f.name, tempName); Str.Append (tempName, "$tmp*");
i := SHORT (Str.Length (tempName)) - 1;
tempName [i] := CHR (f.tempKey);
Delete (tempName, f.dosError)
END; (* ELSE *)
ELSE f.dosError := Dos.base.IoErr ()
END;
END; (* IF *)
END Purge;
(*------------------------------------*)
PROCEDURE Length * (f : File) : LONGINT;
BEGIN (* Length *)
(* ASSERT (f # NIL, 137); *)
RETURN f.len
END Length;
(*------------------------------------*)
PROCEDURE GetDate * (f : File; VAR t, d : LONGINT);
BEGIN (* GetDate *)
(* ASSERT (f # NIL, 137); *)
t := f.time; d := f.date
END GetDate;
(*------------------------------------*)
PROCEDURE Set * (VAR r : Rider; f : File; pos : LONGINT);
BEGIN (* Set *)
r.eof := FALSE; r.res := 0; r.file := f;
IF f # NIL THEN
IF pos < 0 THEN
r.apos := 0; r.bpos := 0
ELSE
r.bpos := SHORT (pos MOD SectorSize); r.apos := pos - r.bpos
END;
r.buf := f.firstbuf
END
END Set;
(*------------------------------------*)
PROCEDURE Read * (VAR r : Rider; VAR x : SYS.BYTE);
VAR buf : Buffer;
BEGIN (* Read *)
(* ASSERT (r.file # NIL, 137); *)
(* ASSERT (r.buf # NIL, 137); *)
IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
IF r.bpos < r.buf.lim THEN
x := r.buf.data [r.bpos]; INC (r.bpos)
ELSIF (r.apos + SectorSize) < r.file.len THEN
INC (r.apos, SectorSize);
r.buf := GetBuf (r.file, r.apos);
x := r.buf.data [0]; r.bpos := 1
ELSE
x := 0X; r.eof := TRUE
END
END Read;
(*------------------------------------*)
PROCEDURE ReadBytes *
( VAR r : Rider; VAR x : ARRAY OF SYS.BYTE; n : LONGINT);
VAR src, dst, m : LONGINT;
buf : Buffer;
BEGIN (* ReadBytes *)
(* ASSERT (r.file # NIL, 137); *)
(* ASSERT (r.buf # NIL, 137); *)
dst := SYS.VAL (LONGINT, SYS.ADR (x));
IF LEN (x) < n THEN HALT (25) END;
IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
LOOP
IF n <= 0 THEN EXIT END;
src := SYS.VAL (LONGINT, SYS.ADR(r.buf.data));
INC (src, r.bpos); m := r.bpos + n;
IF m <= r.buf.lim THEN
SYS.MOVE (src, dst, n); r.bpos := SHORT (m); r.res := 0;
EXIT
ELSIF r.buf.lim = SectorSize THEN
m := r.buf.lim - r.bpos;
IF m > 0 THEN
SYS.MOVE (src, dst, m); INC (dst, m); DEC (n, m)
END;
IF r.apos < r.file.len THEN
INC (r.apos, SectorSize);
r.bpos := 0; r.buf := GetBuf (r.file, r.apos);
ELSE
r.res := n; r.eof := TRUE; EXIT
END; (* ELSE *)
ELSE
m := r.buf.lim - r.bpos;
IF m > 0 THEN
SYS.MOVE (src, dst, m); r.bpos := r.buf.lim
END;
r.res := n - m; r.eof := TRUE; EXIT
END; (* ELSE *)
END; (* LOOP *)
END ReadBytes;
(*------------------------------------*)
PROCEDURE Write * (VAR r : Rider; x : SYS.BYTE);
VAR f : File; buf : Buffer;
BEGIN (* Write *)
(* ASSERT (r.file # NIL, 137); *)
(* ASSERT (r.buf # NIL, 137); *)
IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
IF r.bpos >= r.buf.lim THEN
IF r.bpos < SectorSize THEN
INC (r.buf.lim); INC (r.file.len)
ELSE
f := r.file; INC (r.apos, SectorSize);
r.buf := GetBuf (f, r.apos);
IF r.apos >= f.len THEN r.buf.lim := 1; f.len := r.apos END;
r.bpos := 0
END
END;
r.buf.data [r.bpos] := x; INC (r.bpos); r.buf.mod := TRUE
END Write;
(*------------------------------------*)
PROCEDURE WriteBytes *
(VAR r : Rider; VAR x : ARRAY OF SYS.BYTE; n : LONGINT);
VAR src, dst, m : LONGINT; f : File; buf : Buffer;
BEGIN (* WriteBytes *)
(* ASSERT (r.file # NIL, 137); *)
(* ASSERT (r.buf # NIL, 137); *)
src := SYS.VAL (LONGINT, SYS.ADR (x));
IF LEN (x) < n THEN HALT (25) END;
IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
LOOP
IF n <= 0 THEN EXIT END;
r.buf.mod := TRUE;
dst := SYS.VAL (LONGINT, SYS.ADR(r.buf.data)); INC (dst, r.bpos);
m := r.bpos + n;
IF m <= r.buf.lim THEN
SYS.MOVE (src, dst, n); r.bpos := SHORT (m); EXIT
ELSIF m <= SectorSize THEN
SYS.MOVE (src, dst, n); r.bpos := SHORT (m);
INC (r.file.len, n); r.buf.lim := SHORT (m); EXIT
ELSE
m := SectorSize - r.bpos;
IF m > 0 THEN
SYS.MOVE (src, dst, m); INC (src, m); DEC (n, m);
INC (r.buf.lim, SHORT (m))
END;
f := r.file; INC (r.apos, SectorSize);
r.bpos := 0; r.buf := GetBuf (f, r.apos);
IF r.apos >= f.len THEN r.buf.lim := 0; f.len := r.apos END;
END;
END; (* LOOP *)
END WriteBytes;
(*------------------------------------*)
PROCEDURE Pos * (VAR r : Rider) : LONGINT;
BEGIN (* Pos *)
RETURN r.apos + r.bpos
END Pos;
(*------------------------------------*)
PROCEDURE Base * (VAR r : Rider) : File;
BEGIN (* Base *)
RETURN r.file;
END Base;
(*------------------------------------*)
PROCEDURE InitTempKey ();
VAR time, date : LONGINT;
BEGIN (* InitTempKey *)
Oberon.GetClock (time, date); tempKey := date * 10000H + time;
IF tempKey = 0 THEN INC (tempKey) END
END InitTempKey;
(*------------------------------------*)
PROCEDURE* Cleanup ();
BEGIN
WHILE files # NIL DO
IF files.fileHandle # NIL THEN
Unbuffer (files); Dos.base.OldClose (files.fileHandle)
END;
files := files.next
END;
END Cleanup;
BEGIN
InitTempKey();
files := NIL; SYS.SETCLEANUP (Cleanup)
END Files.
(***************************************************************************
$Log: Files.mod $
Revision 1.6 1994/08/08 16:41:14 fjc
Release 1.4
Revision 1.5 1994/06/14 02:14:31 fjc
- Updated for release
Revision 1.4 1994/06/09 14:12:41 fjc
- Incorporated changes to Amiga interface
Revision 1.3 1994/06/04 16:03:39 fjc
- Changed to use new Amiga interface
Revision 1.2 1994/05/12 20:45:18 fjc
- Prepared for release
Revision 1.1 1994/01/15 21:39:12 fjc
Start of revision control
13 Jan 94 [FJC] : Chinged GetBuf to keep buffers in position
order, hopefully eliminating seek errors.
Now no procedures call ReadBuf or WriteBuf directly.
28 Dec 93 [FJC] : Actually, I had totally stuffed the handling
of temporary files. *This* time, hopefully,
it is fixed.
15 Dec 93 [FJC] : *Really* fixed handling of temporary files.
2 Dec 93 [FJC] : Fixed handling of temporary files.
***************************************************************************)